/*
 * BLLib.Configuration.OptionSet
 * Andy Tidball
 * 
 * Project: Black Lightning General Library
 * Copyright: GNU General Public License
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Windows.Forms;
using BLLib.Xml;

namespace BLLib.Configuration {
	/// <summary>
	/// Represents a set of Options that can be displayed in a PropertyGrid.
	/// </summary>
	public class OptionSet : ICustomTypeDescriptor, ICloneable {
		/// <summary>
		/// The name of the XML tag that this class saves its data to.
		/// </summary>
		public const string XmlTagName          = "options";

		/// <summary>
		/// The name of the XML attribute that stores the OptionSet's name.
		/// </summary>
		public const string XmlNameAttribute    = "name";

		/// <summary>
		/// The name of the XML attribute that stores the OptionSet's default Option.
		/// </summary>
		public const string XmlDefaultAttribute = "default";

		/// <summary>
		/// The name of the XML attribute that stores the OptionSet's order index.
		/// </summary>
		public const string XmlOrderAttribute   = "order";

		// member data
		private string                        _Name;              // the name of the OptionSet
		private Dictionary<string, Option>    _Options;           // a list of Options in the set (maps name to Option)
		private Option                        _Default;           // the default Option
		private Dictionary<string, OptionSet> _Children;          // other OptionSets which are below this one hierarchically (maps name to OptionSet)
		private int                           _IconIndex;         // the index of the icon in the OptionDialog's ImageList to use for the set
		private int                           _SelectedIconIndex; // the index of the icon to use while the set is selected
		private int                           _OrderIndex;        // the index at which the set is displayed when in a list with other OptionSets
		private OptionSet                     _Parent;            // the OptionSet that this one is a child of

		/// <summary>
		/// Creates a new OptionSet.
		/// </summary>
		/// <param name="Name">The name of the new OptionSet.</param>
		public OptionSet(string Name) {
			_Name = Name;
			_Options = new Dictionary<string,Option>();
			_Default = null;
			_Children = new Dictionary<string,OptionSet>();
			_IconIndex = 0;
			_SelectedIconIndex = 0;
			_OrderIndex = 0;
			_Parent = null;
		}

		/// <summary>
		/// Creates a new OptionSet.
		/// </summary>
		private OptionSet()
			: this(string.Empty) {
		}

		/// <summary>
		/// Gets or sets the name of the OptionSet.
		/// </summary>
		public string Name {
			get {
				return _Name;
			}
			set {
				// if we have a parent, remove this set from its listing
				// (because OptionSets map names to objects.. if the name changes we need to reset the mapping)
				if (Parent != null) {
					Parent._Children.Remove(this.Name);
				}

				// store the new name
				_Name = value;

				// if we have a parent, add this set back to it
				if (Parent != null) {
					Parent._Children.Add(_Name, this);
				}
			}
		}

		/// <summary>
		/// Gets a collection which represents the items in the set.  Modifying this collection will have no impact on the set, use the Add/Remove methods instead.
		/// </summary>
		public List<Option> Options {
			get {
				return new List<Option>(_Options.Values);
			}
		}

		/// <summary>
		/// Gets or sets the default Option from among the set's Options.
		/// </summary>
		public Option DefaultOption {
			get {
				return _Default;
			}
			set {
				// make sure the value is either null or an Option from our list
				if (value != null && !HasOption(value.Name)) {
					throw new ArgumentException("The given Option is not in the set.");
				}

				_Default = value;
			}
		}

		/// <summary>
		/// Gets a collection which represents the option sets in the set.  Modifying this collection will have no impact on the set, use the Add/Remove methods instead.
		/// </summary>
		public List<OptionSet> Children {
			get {
				return new List<OptionSet>(_Children.Values);
			}
		}

		/// <summary>
		/// Gets or sets the index of the icon in the OptionDialog's ImageList to display next to the set.
		/// </summary>
		public int IconIndex {
			get {
				return _IconIndex;
			}
			set {
				_IconIndex = value;
			}
		}

		/// <summary>
		/// Gets or sets the index of the icon in the OptionDialog's ImageList to display next to the set while it is selected.
		/// </summary>
		public int SelectedIconIndex {
			get {
				return _SelectedIconIndex;
			}
			set {
				_SelectedIconIndex = value;
			}
		}

		/// <summary>
		/// Gets or sets the index at which this OptionSet is displayed when shown in a list of OptionSets.
		/// </summary>
		public int OrderIndex {
			get {
				return _OrderIndex;
			}
			set {
				_OrderIndex = value;
			}
		}

		/// <summary>
		/// Gets the OptionSet that this one belongs to, or null if it doesn't belong to any.
		/// </summary>
		public OptionSet Parent {
			get {
				return _Parent;
			}
			internal set {
				_Parent = value;
			}
		}

		/// <summary>
		/// Fired whenever the value of an option added via AddOption changes.
		/// </summary>
		public event OptionValueChangedEventHandler OptionValueChanged;

		/// <summary>
		/// Fires the OptionValueChanged event.
		/// </summary>
		/// <param name="Sender">The Option whose value changed.</param>
		/// <param name="e">Arguments about the event.</param>
		protected void OnOptionValueChanged(object Sender, OptionValueChangedEventArgs e) {
			if (OptionValueChanged != null) {
				OptionValueChanged(Sender, e);
			}
		}

		/// <summary>
		/// Fired whenever one of the set's option's permanent values is about to be changed.
		/// </summary>
		public event OptionValueChangingEventHandler OptionValueChanging;

		/// <summary>
		/// Fires the OptionValueChanging event.
		/// </summary>
		/// <param name="Sender">The Option whose value is changing.</param>
		/// <param name="e">Arguments to pass to the event.</param>
		protected void OnOptionValueChanging(object Sender, OptionValueChangingEventArgs e) {
			if (OptionValueChanging != null) {
				OptionValueChanging(Sender, e);
			}
		}

		/// <summary>
		/// Fired whenever the temporary value of an option added via AddOption changes.
		/// </summary>
		internal event OptionValueChangedEventHandler OptionTemporaryValueChanged;

		/// <summary>
		/// Fires the OptionTemporaryValueChanged event.
		/// </summary>
		/// <param name="Sender">The Option whose value changed.</param>
		/// <param name="e">Arguments about the event.</param>
		protected void OnOptionTemporaryValueChanged(object Sender, OptionValueChangedEventArgs e) {
			if (OptionTemporaryValueChanged != null) {
				OptionTemporaryValueChanged(Sender, e);
			}
		}

		/// <summary>
		/// Fired whenever one of the set's option's temporary values is about to be changed.
		/// </summary>
		internal event OptionValueChangingEventHandler OptionTemporaryValueChanging;

		/// <summary>
		/// Fires the OptionTemporaryValueChanging event.
		/// </summary>
		/// <param name="Sender">The Option whose temporary value is changing.</param>
		/// <param name="e">Arguments to pass to the event.</param>
		protected void OnOptionTemporaryValueChanging(object Sender, OptionValueChangingEventArgs e) {
			if (OptionTemporaryValueChanging != null) {
				OptionTemporaryValueChanging(Sender, e);
			}
		}

		/// <summary>
		/// Adds an Option to the set.  Call this rather than adding it directly via the Options collection.
		/// </summary>
		/// <param name="O">The Option to add to the set.</param>
		public void AddOption(Option O) {
			// wire the option to let us know when its value changes
			O.ValueChanged += OptionValueChangedReceived;
			O.ValueChanging += OptionValueChangingReceived;
			O.TemporaryValueChanged += OptionTemporaryValueChangedReceived;
			O.TemporaryValueChanging += OptionTemporaryValueChangingReceived;

			// if this is the first option added, set it as the default
			if (_Options.Count == 0) {
				_Default = O;
			}

			// set the option's parent to ourselves
			O.Parent = this;

			// add the option to the list
			_Options.Add(O.Name, O);
		}

		/// <summary>
		/// Adds an OptionSet to the set.  Call this rather than adding it directly via the Children collection.
		/// </summary>
		public void AddOptionSet(OptionSet OS) {
			// set the option set's parent to ourselves
			OS.Parent = this;

			// add the set to the list
			_Children.Add(OS.Name, OS);
		}

		/// <summary>
		/// Checks whether a given Option exists in the set.
		/// </summary>
		/// <param name="Name">The name of the Option to check for existance in the set.</param>
		/// <returns>True if the named Option is in the set, false if it isn't.</returns>
		public bool HasOption(string Name) {
			return _Options.ContainsKey(Name);
		}

		/// <summary>
		/// Checks whether a given OptionSet exists in the set.
		/// </summary>
		/// <param name="Name">The name of the OptionSet to check for existance in the set.</param>
		/// <returns>True if the named OptionSet is in the set, false if it isn't.</returns>
		public bool HasOptionSet(string Name) {
			return _Children.ContainsKey(Name);
		}

		/// <summary>
		/// Gets the named Option which must be in the set.
		/// </summary>
		/// <param name="Name">The name of the Option to retrieve.</param>
		/// <returns>The retrieved Option from the set or null if it wasn't found.</returns>
		public Option GetOption(string Name) {
			if (HasOption(Name)) {
				return _Options[Name];
			} else {
				return null;
			}
		}

		/// <summary>
		/// Retrieves the Option from the set with the given name.  If it doesn't exist a new Option is created, added to the set, and returned.
		/// </summary>
		/// <param name="Name">The name of the Option to retrieve or create.</param>
		/// <returns>The requested Option, either straight from the set or created and added.</returns>
		public Option GetOrCreateOption(string Name) {
			if (HasOption(Name)) {
				return GetOption(Name);
			} else {
				Option O = new Option(Name);
				AddOption(O);
				return O;
			}
		}

		/// <summary>
		/// Gets the named OptionSet which must be in the set.
		/// </summary>
		/// <param name="Name">The name of the OptionSet to retrieve.</param>
		/// <returns>The retrieved OptionSet from the set or null if it wasn't found.</returns>
		public OptionSet GetOptionSet(string Name) {
			if (HasOptionSet(Name)) {
				return _Children[Name];
			} else {
				return null;
			}
		}

		/// <summary>
		/// Retrieves the child OptionSet from the set with the given name.  If it doesn't exist a new OptionSet is created, added to the set, and returned.
		/// </summary>
		/// <param name="Name">The name of the OptionSet to retrieve or create.</param>
		/// <returns>The requested OptionSet, either straight from the set or created and added.</returns>
		public OptionSet GetOrCreateOptionSet(string Name) {
			if (HasOptionSet(Name)) {
				return GetOptionSet(Name);
			} else {
				OptionSet OS = new OptionSet(Name);
				AddOptionSet(OS);
				return OS;
			}
		}

		/// <summary>
		/// Removes the given Option from the set.
		/// </summary>
		/// <param name="Name">The name of the Option to remove from the set.</param>
		public void RemoveOption(string Name) {
			if (!HasOption(Name)) {
				return;
			}

			Option O = GetOption(Name);
			O.Parent = null;
			_Options.Remove(Name);
		}

		/// <summary>
		/// Removes the given OptionSet from the set.
		/// </summary>
		/// <param name="Name">The name of the OptionSet to remove from the set.</param>
		public void RemoveOptionSet(string Name) {
			if (!HasOptionSet(Name)) {
				return;
			}

			OptionSet OS = GetOptionSet(Name);
			OS.Parent = null;
			_Children.Remove(Name);
		}

		/// <summary>
		/// Returns a list of this OptionSets child sets which are sorted according to their OrderIndex.
		/// </summary>
		/// <param name="Comparer">A comparer to use to sort the OptionSet's children.  OptionSetIndexSorter and OptionSetNameSorter are both ready-to-use.</param>
		/// <returns>A sorted list of child OptionSets.</returns>
		public List<OptionSet> GetSortedChildren(IComparer<OptionSet> Comparer) {
			// grab a list, sort, and return
			List<OptionSet> Sorted = Children;
			if (Comparer != null) {
				Sorted.Sort(Comparer);
			}
			return Sorted;
		}

		/// <summary>
		/// Checks whether or not all Options in the OptionSet (and optionally all child OptionSets) are initialized.
		/// </summary>
		/// <param name="Recursive">Whether or not to recursively check all child OptionSets as well.</param>
		/// <returns>True if this OptionSet (or a child OptionSet if Recursive is true) has at least one Option which is not initialized, false otherwise.</returns>
		public bool AllOptionsInitialized(bool Recursive) {
			// check all of our options
			foreach (Option O in Options) {
				if (!O.Initialized) {
					return false;
				}
			}

			// if we're checking recursively, check all of our children
			if (Recursive) {
				foreach (OptionSet OS in Children) {
					if (!OS.AllOptionsInitialized(Recursive)) {
						return false;
					}
				}
			}

			// they must all be initialized
			return true;
		}

		/// <summary>
		/// Checks whether or not any Options in the OptionSet (and optionally all child OptionSets) are initialized.
		/// </summary>
		/// <param name="Recursive">Whether or not to recursively check all child OptionSets as well.</param>
		/// <returns>True if this OptionSet (or a child OptionSet if Recursive is true) has at least one Option which is initialized, false otherwise.</returns>
		public bool AnyOptionsInitialized(bool Recursive) {
			// check all of our options
			foreach (Option O in Options) {
				if (O.Initialized) {
					return true;
				}
			}

			// if we're checking recursively, check all of our children
			if (Recursive) {
				foreach (OptionSet OS in Children) {
					if (OS.AnyOptionsInitialized(Recursive)) {
						return true;
					}
				}
			}

			// must not be any initialized
			return false;
		}

		/// <summary>
		/// Checks whether or not this OptionSet (and optionally all child OptionSets) have any Options.
		/// </summary>
		/// <param name="Recursive">Whether or not to recursively check all child OptionSets as well.</param>
		/// <returns>True if this OptionSet (or a child OptionSet if Recursive is true) has at least one Option, false otherwise.</returns>
		public bool HasOptions(bool Recursive) {
			// check if we have any options
			if (_Options.Count > 0) {
				return true;
			}

			// if we're checking recursively, check all of our children
			if (Recursive) {
				foreach (OptionSet OS in Children) {
					if (OS.HasOptions(Recursive)) {
						return true;
					}
				}
			}

			// we must not have any options
			return false;
		}

		/// <summary>
		/// Calls Uninitialize on all Options in the set.
		/// </summary>
		/// <param name="Recursive">Whether or not to call Uninitialize recursively on child OptionSets as well.</param>
		public void UninitiazeAll(bool Recursive) {
			// uninitialize all our options
			foreach (Option O in Options) {
				O.Uninitialize();
			}

			// if we're uninitializing recursively, do all of our children
			if (Recursive) {
				foreach (OptionSet OS in Children) {
					OS.UninitiazeAll(Recursive);
				}
			}
		}

		/// <summary>
		/// Calls the equivalent function on all OptionSets and Options in the set.
		/// </summary>
		/// <param name="ValueIndex">The index to check if any options have a value at.</param>
		/// <returns>True if all child OptionSets and Options have a value at the given index, false if not.</returns>
		public bool HasValueAtIndex(int ValueIndex) {
			foreach (OptionSet OS in Children) {
				if (!OS.HasValueAtIndex(ValueIndex)) {
					return false;
				}
			}
			foreach (Option O in Options) {
				if (!O.HasValueAtIndex(ValueIndex)) {
					return false;
				}
			}
			return true;
		}

		/// <summary>
		/// Calls the equivalent function on all OptionSets and Options in the set.
		/// </summary>
		public void ApplyNewValue() {
			ApplyNewValue(0);
		}

		/// <summary>
		/// Calls the equivalent function on all OptionSets and Options in the set.
		/// </summary>
		/// <param name="ValueIndex">The index of the value to apply.</param>
		public void ApplyNewValue(int ValueIndex) {
			ApplyNewValue(ValueIndex, true);
		}

		/// <summary>
		/// Calls the equivalent function on all OptionSets and Options in the set.
		/// </summary>
		/// <param name="ValueIndex">The index of the value to apply.</param>
		/// <param name="CheckForChange">Whether or not to check if the value changed before applying the new value.</param>
		public void ApplyNewValue(int ValueIndex, bool CheckForChange) {
			foreach (OptionSet OS in Children) {
				OS.ApplyNewValue(ValueIndex, CheckForChange);
			}
			foreach (Option O in Options) {
				O.ApplyNewValue(ValueIndex, CheckForChange);
			}
		}

		/// <summary>
		/// Calls the equivalent function on all OptionSets and Options in the set.
		/// </summary>
		public void ApplyNewValues() {
			ApplyNewValues(true);
		}

		/// <summary>
		/// Calls the equivalent function on all OptionSets and Options in the set.
		/// </summary>
		/// <param name="CheckForChange">Whether or not to check if the value changed before applying the new value.</param>
		public void ApplyNewValues(bool CheckForChange) {
			foreach (OptionSet OS in Children) {
				OS.ApplyNewValues(CheckForChange);
			}
			foreach (Option O in Options) {
				O.ApplyNewValues(CheckForChange);
			}
		}

		/// <summary>
		/// Calls the equivalent function on all OptionSets and Options in the set.
		/// </summary>
		/// <param name="ValueIndex">The index of the value to cancel.</param>
		public void CancelNewValue(int ValueIndex) {
			CancelNewValue(ValueIndex, true);
		}

		/// <summary>
		/// Calls the equivalent function on all OptionSets and Options in the set.
		/// </summary>
		/// <param name="ValueIndex">The index of the value to cancel.</param>
		/// <param name="CheckForChange">Whether or not to check if the value was changed before overwriting the old value.</param>
		public void CancelNewValue(int ValueIndex, bool CheckForChange) {
			foreach (OptionSet OS in Children) {
				OS.CancelNewValue(ValueIndex, CheckForChange);
			}
			foreach (Option O in Options) {
				O.CancelNewValue(ValueIndex, CheckForChange);
			}
		}

		/// <summary>
		/// Calls the equivalent function on all OptionSets and Options in the set.
		/// </summary>
		public void CancelNewValues() {
			CancelNewValues(true);
		}

		/// <summary>
		/// Calls the equivalent function on all OptionSets and Options in the set.
		/// </summary>
		/// <param name="CheckForChange">Whether or not to check if the value was changed before overwriting the old value.</param>
		public void CancelNewValues(bool CheckForChange) {
			foreach (OptionSet OS in Children) {
				OS.CancelNewValues(CheckForChange);
			}
			foreach (Option O in Options) {
				O.CancelNewValues(CheckForChange);
			}
		}

		/// <summary>
		/// Checks if the values of any Option in the set or its sub-sets has changed.
		/// </summary>
		/// <param name="ValueIndex">The index of the value to check.</param>
		/// <returns>True if the permanent and temporary values at the given index of any Option in the set differ, false if none of them do.</returns>
		public bool HasValueChanged(int ValueIndex) {
			foreach (OptionSet OS in Children) {
				if (OS.HasValueChanged(ValueIndex)) {
					return true;
				}
			}
			foreach (Option O in Options) {
				if (O.HasValueChanged(ValueIndex)) {
					return true;
				}
			}
			return false;
		}

		/// <summary>
		/// Saves the set of options to an XML tag.
		/// </summary>
		/// <returns>An XML tag containing the names and values of all options in the set, or null if no child Options or OptionSets have values at the given index.</returns>
		public XmlTag Save() {
			return Save(0);
		}

		/// <summary>
		/// Saves the set of options to an XML tag.
		/// </summary>
		/// <param name="ValueIndex">The index of the Option value to save.</param>
		/// <returns>An XML tag containing the names and values of all options in the set, or null if no child Options or OptionSets have values at the given index.</returns>
		public XmlTag Save(int ValueIndex) {
			return Save(ValueIndex, true);
		}

		/// <summary>
		/// Saves the set of options to an XML tag.
		/// </summary>
		/// <param name="IgnoreDuplicateValues">Passed down to Option.Save, see its documentation for more information.</param>
		/// <returns>An XML tag containing the names and values of all options in the set, or null if no child Options or OptionSets have values at the given index.</returns>
		public XmlTag Save(bool IgnoreDuplicateValues) {
			return Save(0, IgnoreDuplicateValues);
		}

		/// <summary>
		/// Saves the set of options to an XML tag.
		/// </summary>
		/// <param name="ValueIndex">The index of the Option value to save.</param>
		/// <param name="IgnoreDuplicateValues">Passed down to Option.Save, see its documentation for more information.</param>
		/// <returns>An XML tag containing the names and values of all options in the set, or null if no child Options or OptionSets have values at the given index.</returns>
		public XmlTag Save(int ValueIndex, bool IgnoreDuplicateValues) {
			// build a list of child tags
			List<XmlTag> ChildTags = new List<XmlTag>();
			foreach (Option O in Options) {
				XmlTag Child = O.Save(ValueIndex, IgnoreDuplicateValues);
				if (Child != null) {
					ChildTags.Add(Child);
				}
			}
			foreach (OptionSet OS in Children) {
				XmlTag Child = OS.Save(ValueIndex, IgnoreDuplicateValues);
				if (Child != null) {
					ChildTags.Add(Child);
				}
			}

			// if we don't have any child tags, this tag isn't necessary either
			if (ChildTags.Count == 0) {
				return null;
			}

			// create the attributes for our tag
			Dictionary<string, string> Attributes = new Dictionary<string,string>(3);
			Attributes.Add(XmlNameAttribute, Name);
			if (OrderIndex != 0) {
				Attributes.Add(XmlOrderAttribute, OrderIndex.ToString());
			}
			if (DefaultOption != null) {
				Attributes.Add(XmlDefaultAttribute, (DefaultOption.Name));
			}

			// return a new tag with the attributes and children
			return new XmlTag(XmlTagName, Attributes, ChildTags);
		}

		/// <summary>
		/// Loads a set of options from an XML tag.
		/// </summary>
		/// <param name="Tag">The XML tag to load the set of options from.</param>
		public void Load(XmlTag Tag) {
			Load(Tag, 0);
		}

		/// <summary>
		/// Loads a set of options from an XML tag.
		/// </summary>
		/// <param name="Tag">The XML tag to load the set of options from.</param>
		/// <param name="ValueIndex">The index of Option value to load from the tag.</param>
		public void Load(XmlTag Tag, int ValueIndex) {
			// make sure we have an option set tag
			if (Tag.Name != XmlTagName) {
				throw new ArgumentException("Given XML tag's name is '" + Tag.Name + "' where '" + XmlTagName + "' was expected.");
			}

			// save our name if we don't have one yet
			if (_Name == string.Empty || _Name == null) {
				_Name = Tag.Attributes[XmlNameAttribute];
			}

			// read in our order index, if present
			if (Tag.Attributes.ContainsKey(XmlOrderAttribute)) {
				_OrderIndex = int.Parse(Tag.Attributes[XmlOrderAttribute]);
			}

			// create our options and children if we don't have any yet
			foreach (XmlTag T in Tag.Children) {
				// check which type of tag this is
				switch (T.Name) {
					case Option.XmlTagName: {
						// find the option in question
						string OptionName = T.Attributes[Option.XmlNameAttribute];
						Option O = GetOrCreateOption(OptionName);

						// load the value into the option
						O.Load(T, ValueIndex);

						// check if it's the default
						if (Tag.Attributes.ContainsKey(XmlDefaultAttribute) && OptionName == Tag.Attributes[XmlDefaultAttribute]) {
							_Default = O;
						}

						break;
					}
					case OptionSet.XmlTagName: {
						// find the option set in question
						string OptionSetName = T.Attributes[XmlNameAttribute];
						OptionSet OS = GetOrCreateOptionSet(OptionSetName);

						// load the option set
						OS.Load(T, ValueIndex);

						break;
					}
				}
			}
		}

		/// <summary>
		/// Clones the OptionSet.  While each Option is cloned, its values may not be deep copied if they are reference types which don't implement ICloneable; see Option.Clone().
		/// </summary>
		/// <returns>A deep copy of the OptionSet.</returns>
		public object Clone() {
			OptionSet OS          = new OptionSet();
			OS._IconIndex         = this._IconIndex;
			OS._Name              = this._Name;
			OS._OrderIndex        = this._OrderIndex;
			OS._SelectedIconIndex = this._SelectedIconIndex;

			// run through and clone all of our options
			OS._Options = new Dictionary<string,Option>(this._Options.Count);
			foreach (KeyValuePair<string, Option> Pair in this._Options) {
				// clone the option
				Option O = Pair.Value.Clone() as Option;

				// if this option is our default, make the cloned option the new set's default
				if (this._Default == Pair.Value) {
					OS._Default = O;
				}

				// add the new option to the new set's list
				OS.AddOption(O);
			}

			// copy the event handlers (except for the parent's)
			if (this.OptionValueChanged != null) {
				foreach (Delegate D in this.OptionValueChanged.GetInvocationList()) {
					OS.OptionValueChanged += D as OptionValueChangedEventHandler;
				}
				if (this.Parent != null) {
					OS.OptionValueChanged -= this.Parent.OptionValueChangedReceived;
				}
			}
			if (this.OptionValueChanging != null) {
				foreach (Delegate D in this.OptionValueChanging.GetInvocationList()) {
					OS.OptionValueChanging += D as OptionValueChangingEventHandler;
				}
				if (this.Parent != null) {
					OS.OptionValueChanging -= this.Parent.OptionValueChangingReceived;
				}
			}
			if (this.OptionTemporaryValueChanged != null) {
				foreach (Delegate D in this.OptionTemporaryValueChanged.GetInvocationList()) {
					OS.OptionTemporaryValueChanged += D as OptionValueChangedEventHandler;
				}
				if (this.Parent != null) {
					OS.OptionTemporaryValueChanged -= this.Parent.OptionTemporaryValueChangedReceived;
				}
			}
			if (this.OptionTemporaryValueChanging != null) {
				foreach (Delegate D in this.OptionTemporaryValueChanging.GetInvocationList()) {
					OS.OptionTemporaryValueChanging += D as OptionValueChangingEventHandler;
				}
				if (this.Parent != null) {
					OS.OptionTemporaryValueChanging -= this.Parent.OptionTemporaryValueChangingReceived;
				}
			}

			// run through and clone all of our child sets
			OS._Children = new Dictionary<string,OptionSet>(this._Children.Count);
			foreach (KeyValuePair<string, OptionSet> Pair in this._Children) {
				OS.AddOptionSet(Pair.Value.Clone() as OptionSet);
			}

			// return the clone
			return OS;
		}

		/// <summary>
		/// Calls the other GetProperties with no attributes.
		/// </summary>
		/// <returns>A collection of descriptors describing the "properties" (Options) of this set.</returns>
		public PropertyDescriptorCollection GetProperties() {
			return GetProperties(new Attribute[0]);
		}

		/// <summary>
		/// Gets a list of descriptors describing the "properties" (Options) of this set.
		/// </summary>
		/// <param name="Attributes">Attributes applied to the OptionSet?</param>
		/// <returns>A collection of descriptors describing the "properties" (Options) of this set.</returns>
		public PropertyDescriptorCollection GetProperties(Attribute[] Attributes) {
			// run through each option in the set and make a descriptor for it
			PropertyDescriptor[] Properties = new PropertyDescriptor[_Options.Count];
			int i=0;
			foreach (Option O in Options) {
				Properties[i] = new OptionDescriptor(O);
				++i;
			}
			return new PropertyDescriptorCollection(Properties);
		}

		/// <summary>
		/// Gets a descriptor for the set's default "property" (Option).
		/// </summary>
		/// <returns>A descriptor for the set's default "property" (Option).</returns>
		public PropertyDescriptor GetDefaultProperty() {
			// return a descriptor for our default option
			return new OptionDescriptor(_Default);
		}

		/// <summary>
		/// Retrieves the property which is being described by the given descriptor.
		/// </summary>
		/// <param name="Descriptor">The descriptor which is describing a "property" (Option).</param>
		/// <returns>The Option being described by the given descriptor, or null if the argument is null or not an OptionDescriptor.</returns>
		public object GetPropertyOwner(PropertyDescriptor Descriptor) {
			// if the given descriptor is null, return the default option
			if (Descriptor == null) {
				return _Default;
			}

			// cast the given descriptor down to an OptionDescriptor
			OptionDescriptor OptionDesc = Descriptor as OptionDescriptor;
			if (OptionDesc == null) {
				// if it didn't cast then return the default option
				return _Default;
			} else {
				// if it did, return the descriptor's option
				return OptionDesc.Option;
			}
		}

		/// <summary>
		/// Passed up to TypeDescriptor.
		/// </summary>
		public AttributeCollection GetAttributes() {
			return TypeDescriptor.GetAttributes(this, true);
		}

		/// <summary>
		/// Passed up to TypeDescriptor.
		/// </summary>
		public string GetClassName() {
			return TypeDescriptor.GetClassName(this, true);
		}

		/// <summary>
		/// Passed up to TypeDescriptor.
		/// </summary>
		public string GetComponentName() {
			return TypeDescriptor.GetComponentName(this, true);
		}

		/// <summary>
		/// Passed up to TypeDescriptor.
		/// </summary>
		public TypeConverter GetConverter() {
			return TypeDescriptor.GetConverter(this, true);
		}

		/// <summary>
		/// Passed up to TypeDescriptor.
		/// </summary>
		public EventDescriptor GetDefaultEvent() {
			return TypeDescriptor.GetDefaultEvent(this, true);
		}

		/// <summary>
		/// Passed up to TypeDescriptor.
		/// </summary>
		public object GetEditor(Type EditorBaseType) {
			return TypeDescriptor.GetEditor(this, EditorBaseType, true);
		}

		/// <summary>
		/// Passed up to TypeDescriptor.
		/// </summary>
		public EventDescriptorCollection GetEvents(Attribute[] Attributes) {
			return TypeDescriptor.GetEvents(this, Attributes, true);
		}

		/// <summary>
		/// Passed up to TypeDescriptor.
		/// </summary>
		public EventDescriptorCollection GetEvents() {
			return TypeDescriptor.GetEvents(this, true);
		}

		/// <summary>
		/// Handles an Option's ValueChanged event.
		/// </summary>
		/// <param name="Sender">The Option whose value changed.</param>
		/// <param name="e">Arguments about the value change.</param>
		internal void OptionValueChangedReceived(object Sender, OptionValueChangedEventArgs e) {
			// fire our own event
			OnOptionValueChanged(Sender, e);
		}

		/// <summary>
		/// Handles an Option's ValueChanging event.
		/// </summary>
		/// <param name="Sender">The Option whose value is changing.</param>
		/// <param name="e">Arguments about the value change.</param>
		internal void OptionValueChangingReceived(object Sender, OptionValueChangingEventArgs e) {
			// fire our own event
			OnOptionValueChanging(Sender, e);
		}

		/// <summary>
		/// Handles an Option's TemporaryValueChanged event.
		/// </summary>
		/// <param name="Sender">The Option whose temporary value changed.</param>
		/// <param name="e">Arguments about the value change.</param>
		internal void OptionTemporaryValueChangedReceived(object Sender, OptionValueChangedEventArgs e) {
			// fire our own event
			OnOptionTemporaryValueChanged(Sender, e);
		}
		
		/// <summary>
		/// Handles an Option's TemporaryValueChanging event.
		/// </summary>
		/// <param name="Sender">The Option whose temporary value is changing.</param>
		/// <param name="e">Arguments about the value change.</param>
		internal void OptionTemporaryValueChangingReceived(object Sender, OptionValueChangingEventArgs e) {
			// fire our own event
			OnOptionTemporaryValueChanging(Sender, e);
		}
	}

	/// <summary>
	/// Sorts OptionSets by their OrderIndex.
	/// </summary>
	public class OptionSetIndexSorter : IComparer<OptionSet> {
		/// <summary>
		/// Compares two OptionSets.
		/// </summary>
		/// <param name="x">The first OptionSet to compare.</param>
		/// <param name="y">The second OptionSet to compare.</param>
		/// <returns>&lt;0 if x&lt;y, &gt;0 if x&gt;y, 0 if x==y</returns>
		public int Compare(OptionSet x, OptionSet y) {
			return x.OrderIndex.CompareTo(y.OrderIndex);
		}
	}
	
	/// <summary>
	/// Sorts OptionSets by their Name.
	/// </summary>
	public class OptionSetNameSorter : IComparer<OptionSet> {
		/// <summary>
		/// Compares two OptionSets.
		/// </summary>
		/// <param name="x">The first OptionSet to compare.</param>
		/// <param name="y">The second OptionSet to compare.</param>
		/// <returns>&lt;0 if x&lt;y, &gt;0 if x&gt;y, 0 if x==y</returns>
		public int Compare(OptionSet x, OptionSet y) {
			return x.Name.CompareTo(y.Name);
		}
	}
}
